// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: chensili

#pragma once

#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/options_product.h"
#include "coin2/exchange/otc/symbology/currency.h"

namespace coin2::exchange::otc_options::symbology {

using coin2::exchange::otc::symbology::OtcCurrency;
using coin2::exchange::base::symbology::StringToOptionsSide;
using coin2::exchange::base::symbology::IsDateStr;
using coin2::exchange::base::symbology::ToDateStr;
using coin2::exchange::base::symbology::StringToOptionsType;

class OtcOptionsProduct
  : public base::symbology::CurrencyPairProduct<coin2::exchange::otc::symbology::OtcCurrency>,
    public base::symbology::ProductFromStrMixin<OtcOptionsProduct> {
 public:
  using OtcCurrency = coin2::exchange::otc::symbology::OtcCurrency;
  using OptionsProduct = base::symbology::OptionsProduct<
      OtcCurrency, OtcOptionsProduct>;
  using OptionsSide = coin::proto::OptionsSide;
  using OptionsType = coin::proto::OptionsType;

  OtcOptionsProduct(
      const std::string& counterparty,
      OptionsType options_type,
      const OtcCurrency& base,
      const OtcCurrency& quote,
      const std::string& native_product,
      const int64_t expiry_timestamp,
      const OptionsSide options_side,
      const int64_t transaction_id,
      const int64_t resolve_timestamp)
    : CurrencyPairProduct<OtcCurrency>(
          base,
          quote,
          native_product,
          native_product,
          native_product,
          native_product,
          coin::proto::Options,
          coin::proto::Otc),
      counterparty_(counterparty),
      options_type_(options_type),
      expiry_timestamp_(expiry_timestamp),
      expiry_str_(ToDateStr(expiry_timestamp_)),
      options_side_(options_side),
      transaction_id_(transaction_id),
      resolve_timestamp_(resolve_timestamp) {}

  static std::unique_ptr<OtcOptionsProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    return FromNormStrNoThrow(native_product, resolve_timestamp);
  }

  static std::unique_ptr<OtcOptionsProduct> FromNormStrImpl(
      const std::string& product_str,
      const int64_t resolve_timestamp) {
    bool success;
    std::string counterparty;
    OptionsType options_type;
    std::string base_str;
    std::string quote_str;
    std::string native_product;
    int64_t expiry_timestamp;
    OptionsSide options_side;
    int64_t transaction_id;

    success = ParseOptionsNormStr(
        product_str,
        &counterparty,
        &options_type,
        &base_str,
        &quote_str,
        &native_product,
        &expiry_timestamp,
        &options_side,
        &transaction_id);

    if (success) {
      auto base = OtcCurrency::FromNormStrNoThrow(base_str, resolve_timestamp);
      auto quote = OtcCurrency::FromNormStrNoThrow(quote_str, resolve_timestamp);

      if (base && quote) {
        if (success) {
          return std::make_unique<OtcOptionsProduct>(
              counterparty,
              options_type,
              *base,
              *quote,
              native_product,
              expiry_timestamp,
              options_side,
              transaction_id,
              resolve_timestamp);
        }
      }
    }
    return {};
  }

  IProduct* Clone() const final {
    return new OtcOptionsProduct(
        counterparty_,
        options_type_,
        base_impl(),
        quote_impl(),
        native_product(),
        expiry_timestamp_,
        options_side_,
        transaction_id_,
        resolve_timestamp_);
  }

  const std::string& counterparty() const { return counterparty_; }

  OptionsType options_type() const { return options_type_; }

  int64_t expiry() const { return expiry_timestamp_; }

  const std::string& expiry_str() const { return expiry_str_; }

  OptionsSide options_side() const { return options_side_; }

  int64_t transaction_id() const { return transaction_id_; }

  int64_t resolve_timestamp() const { return resolve_timestamp_; }

 private:
  static bool ParseOptionsNormStr(
      const std::string& options_norm_str,
      std::string* counterparty,
      OptionsType* options_type,
      std::string* base_str,
      std::string* quote_str,
      std::string* native_product,
      int64_t* expiry_timestamp,
      OptionsSide* options_side,
      int64_t* transaction_id) {
    bool success = ParseOtcOptionsNormStr(
        options_norm_str,
        counterparty,
        options_type,
        base_str,
        quote_str,
        native_product,
        expiry_timestamp,
        options_side,
        transaction_id);
    if (!success) {
      success = ParseVanillaOptionsNormStr(
        options_norm_str,
        counterparty,
        options_type,
        base_str,
        quote_str,
        native_product,
        expiry_timestamp,
        options_side,
        transaction_id);
    }
    return success;
  }

  static bool ParseVanillaOptionsNormStr(
      const std::string& options_norm_str,
      std::string* counterparty,
      OptionsType* options_type,
      std::string* base,
      std::string* quote,
      std::string* native_product,
      int64_t* expiry_timestamp,
      OptionsSide* options_side,
      int64_t* transaction_id) {
    std::vector<std::string> out;
    out = Split(options_norm_str, "-.");
    if (out.size() < 5) {
      return false;
    }

    std::string side_str = out.at(3);
    auto side = StringToOptionsSide(side_str);
    if (!side) {
      return false;
    }

    std::string strike_price_str = out.at(4);
    std::string strike_price_multiplier_str = "1";
    if (out.size() >= 6) {
      strike_price_multiplier_str = out.at(5);
    }

    std::stringstream ss;
    std::string base_str = out.at(0);
    std::string quote_str = out.at(1);
    std::string expiry_str = out.at(2);
    if (IsDateStr(expiry_str)) {
      ss << base_str << "-" << quote_str
        << "." << expiry_str << "." << side_str << "." << strike_price_str
        << "." << strike_price_multiplier_str;
    } else {
      return false;
    }

    *counterparty = "";
    *options_type = OptionsType::EUROPEAN_OPTIONS;
    *base = base_str;
    *quote = quote_str;
    *native_product = ss.str();
    *expiry_timestamp = TimestampFromString(expiry_str);
    *options_side = side.value();
    *transaction_id = 0;

    return true;
  }

  static bool ParseOtcOptionsNormStr(
      const std::string& options_norm_str,
      std::string* counterparty,
      OptionsType* options_type,
      std::string* base,
      std::string* quote,
      std::string* native_product,
      int64_t* expiry_timestamp,
      OptionsSide* options_side,
      int64_t* transaction_id) {
    std::vector<std::string> out;
    out = Split(options_norm_str, "-.");
    if (out.size() != 7) {
      return false;
    }

    std::string type_str = out.at(1);
    auto type = StringToOptionsType(type_str);
    if (!type) {
      return false;
    }

    std::string side_str = out.at(5);
    auto side = StringToOptionsSide(side_str);
    if (!side) {
      return false;
    }

    int64_t tid = 0;
    std::string tid_str = out.at(6);
    try {
      tid = stoll(tid_str);
    } catch (std::exception& e) {
      return false;
    }

    std::string counterparty_str = out.at(0);
    std::stringstream ss;
    std::string base_str = out.at(2);
    std::string quote_str = out.at(3);
    std::string expiry_str = out.at(4);
    if (IsDateStr(expiry_str)) {
      ss << counterparty_str << "." << type_str << "."
         << base_str << "-" << quote_str << "."
         << expiry_str << "." << side_str << "." << tid_str;
    } else {
      return false;
    }

    *counterparty = counterparty_str;
    *options_type = type.value();
    *base = base_str;
    *quote = quote_str;
    *native_product = ss.str();
    *expiry_timestamp = TimestampFromString(expiry_str);
    *options_side = side.value();
    *transaction_id = tid;

    return true;
  }

  const std::string counterparty_;
  const OptionsType options_type_;
  const int64_t expiry_timestamp_;
  const std::string expiry_str_;
  const OptionsSide options_side_;
  const int64_t transaction_id_;
  const int64_t resolve_timestamp_;

};

}  // namespace coin2::exchange::deribit_options::symbology
